package net.studou.secret;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import net.studou.annotation.FixedKeyAnnotation;
import net.studou.annotation.SecretStatusAnnotation;
import net.studou.annotation.SecretType;
import net.studou.annotation.WillSecretAnnotation;
import net.studou.annotation.SignatureAnnotation;
import net.studou.annotation.SignatureMark;
import net.studou.exception.SecretException;

public class SecretUtils {

	public static <T> T decrypt(T po) throws RuntimeException {
		return SecretUtils.encryptOrDecrpyt(po, SecretType.DECRYPT);
	}

	public static <T> T encrypt(T po) throws RuntimeException {
		return SecretUtils.encryptOrDecrpyt(po, SecretType.ENCRYPT);
	}

	public static <T> T encryptOrDecrpyt(T po, SecretType type) throws RuntimeException {
		Class<?> clazz = po.getClass();
		List<Field> fList = new ArrayList<Field>();

		String fixedKey = null;
		Field secretType = null;
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			try {
				field.setAccessible(true);
				if (field.isAnnotationPresent(FixedKeyAnnotation.class)) {
					fixedKey = field.get(po).toString();
				} else if (field.isAnnotationPresent(WillSecretAnnotation.class)) {
					fList.add(field);
				} else if (field.isAnnotationPresent(SecretStatusAnnotation.class)) {
					secretType = field;
				}
			} catch (Exception e) {
				throw new SecretException("Get field error", e, po, field, null, null, null);
			}
		}
		if (fixedKey != null) {
			for (Field field : fList) {
				String key = null;
				String operateValue = null;
				try {
					WillSecretAnnotation annotation = field.getAnnotation(WillSecretAnnotation.class);
					key = fixedKey + ":" + annotation.value().toString();
					operateValue = field.get(po).toString();

					if (SecretType.DECRYPT.equals(type) && (secretType.get(po) == null || secretType.get(po).equals(SecretType.ENCRYPT))) {
						operateValue = DESCoder.decryptSTD(operateValue, key);
						field.set(po, operateValue);
					}
					if (SecretType.ENCRYPT.equals(type) && (secretType.get(po) == null || secretType.get(po).equals(SecretType.DECRYPT))) {
						operateValue = DESCoder.encryptSTD(operateValue, key);
						field.set(po, operateValue);
					}
				} catch (Exception e) {
					throw new SecretException(type.getDescription() + " error", e, po, field, key, operateValue);
				}
			}
			try {
				secretType.set(po, type);
			} catch (Exception e) {
				throw new SecretException("Set po status error", e, po, secretType, type.name());
			}
		}
		return po;
	}

	public static <T> T signature(T po) throws RuntimeException {
		return SecretUtils.signatureOrUnignature(po, SecretType.SIGNATURE);
	}

	public static <T> T unSignature(T po) throws RuntimeException {
		return SecretUtils.signatureOrUnignature(po, SecretType.UNSIGNATURE);
	}

	public static <T> T signatureOrUnignature(T po, SecretType type) throws RuntimeException {
		Class<?> clazz = po.getClass();
		StringBuffer items = new StringBuffer();
		Field signature = null;
		String fixedKey = null;

		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			try {
				field.setAccessible(true);
				if (field.isAnnotationPresent(FixedKeyAnnotation.class)) {
					fixedKey = field.get(po).toString();
				} else if (field.isAnnotationPresent(SignatureAnnotation.class)) {
					SignatureMark item = field.getAnnotation(SignatureAnnotation.class).value();
					if (SignatureMark.ITEM.equals(item)) {
						items.append(field.get(po));
						items.append(":");
					} else if (SignatureMark.TOTAL.equals(item)) {
						signature = field;
					}
				}
			} catch (Exception e) {
				throw new SecretException("Get field error", e, po, field);
			}
		}
		if (fixedKey != null) {
			String key = fixedKey + "_STUDOU";
			String signatureValue = null;
			try {
				if (SecretType.UNSIGNATURE.equals(type)) {
					signatureValue = signature.get(po).toString();
					//TODO 打开签名
					//signatureValue = DESCoder.decryptSTD(signature.get(po).toString(), key);
					if (!items.toString().equals(signatureValue)) {
						throw new Exception("Signature error");
					}
				}
				if (SecretType.SIGNATURE.equals(type)) {
					signatureValue = items.toString();
					//TODO 打开签名
					//signatureValue = DESCoder.encryptSTD(items.toString(), key);
					signature.set(po, signatureValue);
				}
			} catch (Exception e) {
				throw new SecretException(type.getDescription() + " error", e, po, signature, key, items.toString(), signatureValue);
			}
		}

		return po;
	}

}
